#include <stx/test/test.hpp>

TEST_CASE (core_posix)
{
    CHECK(getExePath() != "/"sv);
    CHECK(getExePath().native().starts_with("/"sv));
    CHECK(getExePath().parent_path() == getExeDir());

    {
        auto fin = File("/bin/bash"_fp, File::OpenMode::K_READONLY);
        CHECK(fin);
        CHECK(isReadable(fin.fd()));
    }

    CHECK(isReadable(FdState::K_READABLE));
    CHECK(isReadable(FdState::K_READABLE_OR_WRITABLE));
    CHECK(!isReadable(FdState::K_WRITABLE));
    CHECK(!isReadable(FdState::K_SYS_ERROR));
    CHECK(!isReadable(FdState::K_TIMEOUT));

    CHECK(isWritable(FdState::K_WRITABLE));
    CHECK(isWritable(FdState::K_READABLE_OR_WRITABLE));
    CHECK(!isWritable(FdState::K_READABLE));
    CHECK(!isWritable(FdState::K_SYS_ERROR));
    CHECK(!isWritable(FdState::K_TIMEOUT));

    {
        CHECK(File{}.fd() < 0);

        auto fp = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::remove(fp));

        fs::touch(fp);

        createThread(
            [&] { CHECK(IN_MODIFY == watchFsEvents(fp, IN_MODIFY).get()); });

        sleepFor(1s);
        appendTo(fp, "hello"sv).get();

        auto fin = File(fp, File::OpenMode::K_MODIFY);
        CHECK(fin);
        CHECK(Number(fin.fd()) == fin.getHashCode());
        CHECK(0 == fin.seek(File::Position::K_CURRENT, 0));
        auto buf = Buffer(C_PAGE_SIZE);
        CHECK(5 == fin.read(buf, 5s).get());
        fin.seek(File::Position::K_BEGIN, 0);
        CHECK(5 == ::read(fin.fd(), buf).get());
        fin.seek(File::Position::K_BEGIN, 0);
        CHECK(5 == ::read(fin.fd(), buf, 5s).get());
        CHECK(5 == fin.write("hello"sv, 5s).get());
        CHECK(10 == fin.seek(File::Position::K_CURRENT, 0));
        CHECK(5 == write(fin.fd(), "hello"sv, 5s).get());
        CHECK(15 == fin.seek(File::Position::K_CURRENT, 0));

        fin = File(fp, File::OpenMode::K_APPEND);
        CHECK(fin);
        CHECK(Number(fin.fd()) == fin.getHashCode());
        CHECK(15 == fin.seek(File::Position::K_CURRENT, 0));

        fin = File(fp, File::OpenMode::K_APPEND, File::Position::K_BEGIN, 3);
        CHECK(fin);
        CHECK(Number(fin.fd()) == fin.getHashCode());
        CHECK(15 == fin.seek(File::Position::K_CURRENT, 0));

        fin = File(fp, File::OpenMode::K_READONLY);
        CHECK(fin);
        CHECK(Number(fin.fd()) == fin.getHashCode());
        CHECK(0 == fin.seek(File::Position::K_CURRENT, 0));

        fin = File(fp, File::OpenMode::K_READONLY, File::Position::K_BEGIN, 3);
        CHECK(fin);
        CHECK(Number(fin.fd()) == fin.getHashCode());
        CHECK(3 == fin.seek(File::Position::K_CURRENT, 0));

        fin = File(fp, File::OpenMode::K_MODIFY);
        CHECK(fin);
        CHECK(fin.fd() >= 0);
        CHECK(Number(fin.fd()) == fin.getHashCode());
        CHECK(0 == fin.seek(File::Position::K_CURRENT, 0));
        fin.close();
        CHECK(fin.fd() < 0);

        fin = File(fp, File::OpenMode::K_MODIFY, File::Position::K_BEGIN, 7);
        CHECK(fin);
        CHECK(Number(fin.fd()) == fin.getHashCode());
        CHECK(7 == fin.seek(File::Position::K_CURRENT, 0));

        CHECK("hellohellohello"sv == readFileToString(fp).get());
        CHECK(MemView("hellohellohello"sv) ==
              MemView(readFileToBuffer(fp).get()));

        CHECK(5 == writeTo(fp, "world"sv).get());
        CHECK(fs::getFileSize(fp) == 5);
        CHECK("world"sv == readFileToString(fp).get());
        CHECK(MemView("world"sv) == MemView(readFileToBuffer(fp).get()));

        CHECK(6 == appendTo(fp, std::vector{MemView("123"sv), MemView("456"sv)})
                       .get());
        CHECK("world123456"sv == readFileToString(fp).get());
        CHECK(MemView("world123456"sv) == MemView(readFileToBuffer(fp).get()));

        CHECK(7 == writeTo(fp, std::vector{MemView("abcd"sv), MemView("xyz"sv)})
                       .get());
        CHECK("abcdxyz"sv == readFileToString(fp).get());
        CHECK(MemView("abcdxyz"sv) == MemView(readFileToBuffer(fp).get()));
    }

    {
        auto tfd = TimerFd(1s, 2s);
        CHECK(!isReadable(tfd.fd()));
        CHECK(!isWritable(tfd.fd()));
        sleepFor(1100ms);
        CHECK(isReadable(tfd.fd()));
        CHECK(!isWritable(tfd.fd()));
        tfd.clear();
        CHECK(!isReadable(tfd.fd()));
        CHECK(!isWritable(tfd.fd()));
        sleepFor(2s);
        CHECK(isReadable(tfd.fd()));
        CHECK(!isWritable(tfd.fd()));
    }

    {
        auto efd = EventFd{};
        CHECK(efd);
        CHECK(!isReadable(efd.fd()));
        efd.notify();
        CHECK(isReadable(efd.fd()));

        CHECK(FdState::K_READABLE == wait(efd.fd(), FdState::K_READABLE).get());
        efd.clear();
        CHECK(FdState::K_TIMEOUT ==
              wait(efd.fd(), FdState::K_READABLE, 1s).get());
    }

    auto is_ok = false;
    createThread([&] { is_ok = true; });
    sleepFor(200ms);
    CHECK(is_ok);

    is_ok = false;
    createThread("test_thrd"s, [&] { is_ok = true; });
    sleepFor(200ms);
    CHECK(is_ok);

    {
        auto fp = "/tmp"_fp / genUuidString();
        ON_SCOPE_EXIT(fs::remove(fp));
        fs::touch(fp);

        if (0 == ::fork())
        {
            auto fmtx = FileMutex{};
            fmtx.lock(fp);
            sleepFor(3s);
            fmtx.unlock();
            std::exit(EXIT_SUCCESS);
        }

        sleepFor(1s);
        auto fmtx = FileMutex{};
        CHECK(!fmtx.lock(fp));
        sleepFor(3s);
        CHECK(fmtx.lock(fp));
        fmtx.unlock();
    }
}